=head1 BASE INFORMATION

=head2  Filename

APIDoc.pm

=head2  Path

/lib/Model/

=head2  Notes

=head2  ToDo's

=head2 Revision$

=over

=item *

$HeadURL: http://catwork.googlecode.com/svn/trunk/lib/catwork/Model/APIDoc.pm $

=item * 

$Id: APIDoc.pm 3 2011-03-21 17:56:59Z hg.winkler.77 $

=back

=head2 Header Source Code

=cut
    
package catwork::Model::APIDoc;
use Moose;
use namespace::autoclean;


use File::Find;
use JSON;
use Data::Dumper;
use HTML::TreeBuilder;

extends 'Catalyst::Model';

my $paths = {};
my $docs = {};
my $current_wanted_path = "";
my $C;

=head1 NAME

catwork::Model::API - Catalyst Model

=head1 DESCRIPTION

Catalyst Model.

=head1 METHODS

=cut

=head2 get_apidoc_list( $c )

find all modules and returns a hash like:

    $list = {
                data => 
                {
                    title => $name,
                    attr => {
                    href => $href,
                        }
                },
                attr => {
                            rel => $rel,
                        },
                children => { CHILDREN },
                state => "open",
            };

=head3 source code

=cut

sub get_apidoc_list {

     my ($self, $c) = @_;
     
     $paths = {};
     $docs = {};
     $C = $c;
     $current_wanted_path = "";
     
     my $apidoc_paths = $c->{apps}->{registry}->{apidoc}->{paths};
     for my $path_name (keys %$apidoc_paths) {
         my $path = $apidoc_paths->{$path_name};
         File::Find::find(\&_wanted, ($path));
     }

     my $apidoc_list = $self->_generate_apidoc_list($c, $paths);
     return $apidoc_list;

}

=head2 pod2html( $c, $path )

given the local path plus the filename to generate html output 
and return that content

=head3 input

    $path = "lib/catwork/Controller/APIDocBrowser.pm";

=head3 return

    $return = {
                  html => $html,
                  warnings => @$warnings
              };

Note: That function returns for the html key pure HTML code!

=head3 source code

=cut

sub pod2html {
    my ($self, $c, $path) = @_;

    my $prepare = $self->_prepare_pod2html($c, $path);
    my $html = `$prepare->{html_cmd}`;
    my $stderr = `$prepare->{stderr_cmd}`;
    my @warnings = split(/\n/, $stderr);

    my $tree = HTML::TreeBuilder->new_from_content($html);
    $tree->elementify();
    my $body = $tree->find('body');
    $html = "";
    foreach my $line ($body->content_list())
    {
        if(ref($line) eq "HTML::Element") {
            $html .= $line->as_HTML();
        }
    }

    return { html => $html , warnings => \@warnings};

}

=head2 search( $c, $search )

this function search over the all perl files to search the given search string
and will return a hash like:

    $return = { script_path => hits };

=head3 source code

=cut

sub search {

    my ($self, $c, $search) = @_;
    my $result = {};
    for my $path (sort keys %$docs) {
        my $scripts = $docs->{$path};
        for my $script (@$scripts) {
            my $script_path = $path.$script;
            $c->log->debug("### Check $script_path ###\n");
            my $pod2text = "/usr/bin/pod2text ../$script_path";
            my $text = `$pod2text 2>&1`;

            $search = $self->_validate_search_string($search);
            my $hits = scalar ($text =~ s/$search/$search/ig);
            
            if($hits > 0) {
                $result->{$script_path}->{hits} = $hits;
                $c->log->debug("### Found something for '$search' and hits $hits ###\n");
            }
        }
    }
    return $result;
    
}

=head2 single_search_result( $c, $path, $search )

given the local path plus the filename and the search string to generate html 
output with marked search values and return that content

=head3 input

    $path = "lib/catwork/Controller/APIDocBrowser.pm";
    $search = "search string";

=head3 return

    $return = {
                  html => $html,
                  warnings => @$warnings
              };

Note: That function returns for the html key pure HTML code!

=head3 source code

=cut

sub single_search_result {

    my ($self, $c, $path, $search) = @_;

    my $prepare = $self->_prepare_pod2html($c, $path);
    my $html = `$prepare->{html_cmd}`;
    my $stderr = `$prepare->{stderr_cmd}`;
    
    my @warnings = split(/\n/, $stderr);

    my $tree = HTML::TreeBuilder->new_from_content($html);
    $tree->elementify();

    my $body = $tree->find('body');
    $html = "";
    
    foreach my $line ($body->content_list())
    {
        if(ref($line) eq "HTML::Element") {
            my $text = $line->as_text();
            if($text =~ m/$search/ig) {
                my $html_tmp = $line->as_HTML();
                # WORKAROUND: this regex will found the $search string between the tags
                #             but it can only replace the first one that he found
                #             if you have more than one only the first one will be marked
                #             thanks to sebastian koehn! he helped me a lot to build the regex
                # TODO: so we need a solution to parse the html tree recursivly to mark
                #       only the values between the tags (HTML:TreeBuilder)
                # $html_tmp =~ s/>([^<]*?)(\Q$search\E)([^<]*?)</>$1<span class='ui-search-result'>$2<\/span>$3</g;
                $html .= $html_tmp;
               
            } 
            else {
                $html .= $line->as_HTML();
            }
            
        }
    }

    return { html => $html , warnings => \@warnings};

}

=head2 generate_json( $c, $value )

returns a JSON string by given hash or array structure

=head3 source code

=cut

sub generate_json {
    
    my ($self, $c, $value) = @_;
    my  $json = JSON->new();
    return $json->encode( $value );

}

=head2 get_project_report( $c )

generate a programming language report for the whole project and returns text 

=head3 source code

=cut

sub get_project_report {
    
    my ($self, $c) = @_;
    my $project_report = `./script/catwork_cloc.pl --exclude-dir ./misc ./ 2>/dev/null`;
    
    return $project_report;
}

=head2 ajax_get_summary UNDER CONSTRUCTION

should return a list of all Docs (with description etc.) in the given path.

=head3 source code

=cut

sub get_summary {
    
    my ($self, $c, $path) = @_;
    
    # generate files array
    my @files;
    for my $doc_path (keys %$docs) {
        
        if($doc_path =~ m/$path/){
            for my $file (@{$docs->{$doc_path}}) {
                push(@files, $doc_path.$file);
            }
        }
    }
    # filter infos from the files
    my $summary;
    for my $path_and_filename (@files) {
         # 1) pod to text
         $c->log->info("### INFO $path_and_filename ###\n");
         my $cmd = "/usr/bin/pod2text ../$path_and_filename";
         my $pod_text = `$cmd`;
         my @pod_text_array = split (/\n/, $pod_text);
         # 2) set summary infos
         $summary->{$path_and_filename}->{desc} = "";
         $summary->{$path_and_filename}->{revision} = "";
         $summary->{$path_and_filename}->{author} = "";
         for my $pod_text_line (@pod_text_array) {

             if($pod_text_line =~ m/^DESCRIPTION/) {
                 $summary->{$path_and_filename}->{desc} = "found";
                 next;
             }
             if($summary->{$path_and_filename}->{desc} eq "found") {
                 $pod_text_line = substr($pod_text_line, 4);
                 $summary->{$path_and_filename}->{desc} = $pod_text_line;
             }

             if($pod_text_line =~ m/^AUTHOR/) {
                 $summary->{$path_and_filename}->{author} = "found";
                 next;
             }
             if($summary->{$path_and_filename}->{author} eq "found") {
                 $pod_text_line = substr($pod_text_line, 4);
                 $summary->{$path_and_filename}->{author} = $pod_text_line;
             }

             if($pod_text_line =~ m/\$Id:/) {
                 $pod_text_line = substr($pod_text_line, 8);
                 $summary->{$path_and_filename}->{revision} = $pod_text_line;
                 next;
             }
         }
    }

    my $result;
    for my $pod_path (keys %$summary) {
        
        my $description = $summary->{$pod_path}->{desc};
        my $author = $summary->{$pod_path}->{author};
        my $revision = $summary->{$pod_path}->{revision};
        
        push(@$result, ["/".$pod_path, $description, $revision, $author]);
        
    }

    return $result;

}

=head2 _prepare_pod2html( $c, $path )

prepare the html command and return it in an hash

    $return = {
        html_cmd => $html_cmd,
        stderr_cmd => $stderr_cmd
    };

=head3 source code

=cut

sub _prepare_pod2html {

    my ($self, $c, $path) = @_;

    my $line_cnt = $self->_check_file_lenght($c, $path);
    my $html_cmd = "";
    my $stderr_cmd = "";
    if($path =~ m/\.(pl|pm|tt2|js)$/ and $line_cnt < 1000) {
        my $sed = "| sed -e \'s/class=\"/class=\"pt-/g\'";
        my $cmd = "/usr/bin/perltidy -html -q -se -st ../$path";
        $html_cmd = "$cmd $sed 2>/dev/null";
        $stderr_cmd = "$cmd 2>&1 1>/dev/null";
    } 
    elsif ($path =~ m/\.(css)$/ or $line_cnt > 1000){
        my $cmd = "/usr/bin/pod2html ../$path";
        $html_cmd = "$cmd 2>/dev/null";
        $stderr_cmd = "$cmd 2>&1 1>/dev/null";
    }

    return {html_cmd => $html_cmd, stderr_cmd => $stderr_cmd };
}


=head2 _check_file_lenght( $c, $path )

return the total line number of one file

=head3 source code

=cut

sub _check_file_lenght {
    
    my ($self, $c, $path) = @_;
    
    # http://www.hidemail.de/blog/wie-viele-zeilen-hat-die-datei-.shtml
    # Die Datei wird geöffnet.
    # Danach wir per read jeweils ein Datenblock von einem Megabyte in $_ eingelesen 
    # und die darin enthaltenen \n's gezählt (tr /\n/\n/) und $cou hinzugefügt.
    open (my $in,"<","../$path");
    my $count = 0;
    $count += tr/\n/\n/ while read ($in,$_,1024000);
    close $in;
    
    return $count;

}

=head2 _generate_apidoc_list( $c, $path_h, $path, $name )

Recursively create a hash tree that represents the directory tree in the hash
refered to the given reference $hash_h.

=head3 input

    $path_h =  {   lib => {
                      'catwork.pm' => {},
                      'catwork' => {
                                        'Controller' => {
                                                            'Labs.pm' => {},
                                                            'Root.pm' => {},
                                                            'APIDocBrowser.pm' => {}
                                                        },
                                        'View' => {
                                                      'TT.pm' => {}
                                                  },
                                        'Model' => {
                                                       'APIDoc.pm' => {}
                                                   }
                                    }
                  }
              };
        
    $path = "lib/catwork/";
    $name = "catwork";

    $path = "lib/bluehom.pm";
    $name = "catwork.pm";

=head3 source code

=cut
 
sub _generate_apidoc_list {

   my $self     = shift;
   my $c        = shift;
   my $path_h   = shift;
   my $path     = shift || "";
   my $name     = shift || "Root";

   my $children;
   # recursively run to generate the jstree hash
   foreach my $dir_name ( sort( keys( %{ $path_h}))) {

      my $full_path = sprintf( q{%s%s/}, $path, $dir_name);
      my $nodes = $self->_generate_apidoc_list($c, $path_h->{$dir_name}, $full_path, $dir_name)
         if( defined $path_h->{$dir_name});

      push(@$children, $nodes);

   }

    # set the node type for jstree
    # TODO: that is not completly correct! because we generate here jstree
    #       specific code, that must be done on the TT file and not in the model
    #       that means we should change the hash format in a more common way
    chop($path);
    my ($rel, $href, $state);
    if($name eq "Root") {
        $rel = "root";
        $href = "javascript:get_apidoc_list()";
        $state = "open";
    }
    elsif (  $name eq "lib" or
             $name eq "catwork" or
             $name eq "root"or
             $name eq "src" or
             $name eq "static" or
             $name eq "js" or
             $name eq "css"
    ) {
        $rel = "folder";
        $href = "javascript:get_summary('$path/');";
        $state = "open";
    }
    elsif ($name =~ m/\.(pl|pm)$/) {
        $rel = "perl_file";
        $href = "javascript:render_apidoc('$path','$name')";
        $state = "close";
    }
    elsif ($name =~ m/\.(tt2)$/) {
        $rel = "tt_file";
        $href = "javascript:render_apidoc('$path','$name')";
        $state = "close";
    }
    elsif ($name =~ m/\.(js)$/) {
        $rel = "js_file";
        $href = "javascript:render_apidoc('$path','$name')";
        $state = "close";
    }
    elsif ($name =~ m/\.(css)$/) {
        $rel = "css_file";
        $href = "javascript:render_apidoc('$path','$name')";
        $state = "close";
    }
    else {
        $rel = "folder";
        $href = "javascript:get_summary('$path/');";
        $state = "close";
    }
    # create node for jstree
    my $node = {
                  data => {
                              title => $name,
                              attr => {
                                  href => $href,
                            }
                  },
                  attr => {
                              rel => $rel,
                          },
                  children => $children,
                  state => $state,
                };

   return $node;

}

=head2 _validate_search_string( $search )

to handle special character like star or brackets

=head3 source code

=cut

sub _validate_search_string {
    
   my $self   = shift;
   my $search = shift;
   if($search) {
      $search =~ s/\*/.*/g;
      $search =~ s/\?/.?/g;
      $search =~ s/([\(\)\[\]])/\\$1/g;
   }
   return $search;

}

=head2 _wanted

Check if a given file matches *.p[lm] and is a file, in that case, add it
to the global $paths and $docs hash.

    $paths =  {   lib => {
                      'catwork.pm' => {},
                      'catwork' => {
                                        'Controller' => {
                                                            'Labs.pm' => {},
                                                            'Root.pm' => {},
                                                            'APIDocBrowser.pm' => {}
                                                        },
                                        'View' => {
                                                      'TT.pm' => {}
                                                  },
                                        'Model' => {
                                                       'APIDoc.pm' => {}
                                                   }
                                    }
                  }
              };
    
    $docs = {
                  'lib/' => [
                                       'catwork.pm'
                                     ],
                  'lib/catwork/Controller/' => [
                                                   'APIDocBrowser.pm',
                                                   'Labs.pm',
                                                   'Root.pm',
                                                   'Demo.pm'
                                                ],
                  'lib/catwork/View/' => [
                                             'TT.pm'
                                          ],
                  'lib/catwork/Model/' => [
                                              'APIDoc.pm'
                                           ]
             };

=head3 source code

=cut

sub _wanted {

    my $found = "";
    $found = $File::Find::name if -f && m/\.(pl|pm|tt2|js|css)$/;
    if ( $found =~ qr{^\.\./(.+/)(.+)$}i) {

       my $path = $1;
       # remove the catwork from the beginning of the path
       # thats is nessesary to use a relative path from inside the project
       # ./lib/catwork or ./root/static/ and so on.....
       # my $path = substr($1, 9);
       
       my $filename   = $2;
       push( @{$docs->{$path}}, $filename);
       # copy hash reverence to a local variable
       my $path_r = $paths;
       while( $path =~ m/([^\/]+)/g) {

          my $folder = $1;
          # create new folder
          $path_r->{$folder} = {} if( not defined $path_r->{$folder});
          # add to current folder the files related to the global $path
          $current_wanted_path .= "$folder/";
          if($current_wanted_path eq $path) {
              $path_r = $path_r->{$folder}->{$filename} = {};
              $current_wanted_path = "";
          }
          # add folder to folder
          else {
              $path_r = $path_r->{$folder};
          };
       }
    }
}

=head1 AUTHOR

Hans-Georg Winkler,,,

=head1 LICENSE

This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.

=cut

__PACKAGE__->meta->make_immutable;

1;
